/*
 * Created by Jaren at 2022/2/21 13:57
 */
import 'dart:async';
import 'dart:io';

import 'package:inmotion_protocol/base/base_message.dart';
import 'package:inmotion_protocol/base/file/file_manager.dart';
import 'package:inmotion_protocol/base/protocol.dart';
import 'package:inmotion_protocol/common/exceptions.dart';
import 'package:inmotion_protocol/common/mode.dart';
import 'package:inmotion_protocol/lorin/constant.dart';
import 'package:inmotion_protocol/protocol_worker.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/device.dart';
import 'package:inmotion_protocol/util/log.dart';

abstract class MediaPkgUploadManager {
  final Protocol protocol;
  MediaUploadConfig? _config;

  bool canceled = true;

  IFileManager? _fileManager;

  StreamController<UploadEvent> streamController = StreamController();

  MediaPkgUploadManager(this.protocol);

  config(MediaUploadConfig config) {
    _fileManager = protocol.createFileUploadManager(config);
    _fileManager!.init(config);
    _config = config;
  }

  Future<bool> checkModeForUpload() {
    if (!protocol.productProperty.model.isEuc()) return Future.value(true);

    return protocol.requestRealTimeState().then((state) {
      return state.pcMode != Mode.working;
    });
  }

  Stream<UploadEvent> start() {
    canceled = false;

    if (streamController.isClosed || streamController.isPaused) {
      try {
        streamController.close();
      } catch (ignore) {}
      streamController = StreamController();
    }

    checkModeForUpload().then((shouldContinue) {
      if (!shouldContinue) {
        streamController.addError(MediaFileUploadError.byModeCheck(MediaFileUploadError.errorNotNow));
        return;
      }

      streamController.add(UploadEvent.byCheckStorageCapacity());

      checkStorageCapacity().then((storageEnough) {
        if (canceled) {
          streamController.add(UploadEvent.byCanceled());
          return;
        }
        if (!storageEnough) throw MediaFileUploadError.byStorageCapacityNotEnough();

        _fileManager!.start().listen((fileEvent) {
          switch (fileEvent.type) {
            case FileEvent.fileOpened:
              streamController.add(UploadEvent.byFileOpened());
              break;
            case FileEvent.fileProgress:
              streamController.add(FileTransferringEvent(fileEvent as FileProgressEvent));
              break;
            case FileEvent.prepareFileChecking:
              break;
            case FileEvent.fileChecked:
              streamController.add(UploadEvent.byFileChecked());
              break;
            case FileEvent.fileClosed:
              streamController.add(UploadEvent.byFileClosed());
              // 文件关闭，传输完成
              // 重命名
              renameFileIfNeed(_config!.id, streamController).then((ignore) {
                streamController.add(UploadEvent.byFinished());
              });
              break;
          }
        }).onError((error) {
          LogUtils.e(error);
          if (error is CanceledError) {
            streamController.add(UploadEvent.byCanceled());
            return;
          }
          if (error is FileOperationStepError) {
            streamController.addError(MediaFileUploadError.byFileOperationStepError(error));
            return;
          }
          streamController.addError(error);
        });
      }).onError((error, stackTrace) {
        LogUtils.e("An error occurred when checking storage capacity.", error);
        if (error is MediaFileUploadError) {
          streamController.addError(error);
        } else {
          streamController.addError(Exception("Unknown error"));
        }
      });
    }).onError((error, stackTrace) {
      if (error is MsgTimeoutException) {
        streamController.addError(MediaFileUploadError.byModeCheck(MediaFileUploadError.errorTimeout));
      }
    });

    return streamController.stream;
  }

  Future<bool> checkStorageCapacityAndClearCacheIfNeed({int? size}) {
//        return checkStorageCapacity().then((storageEnough) {
//            if (!storageEnough) {
//                return clearCacheFiles().then((cleared) {
//                    if (cleared) {
//                        return checkStorageCapacity();
//                    }
//                    return false;
//                });
//            }
//            return true;
//        }).onError((error, stackTrace) {
//            if(error is MsgTimeoutException){
//                throw MediaFileUploadError.byStorageCapacityNotEnough();
//            }
//        });
    return Future.value(true);
  }

  Future<bool> checkStorageCapacity({int? size}) {
    if (size == null) {
      if (_config!.localFile == FileConfig.invalidFile) {
        throw InvalidParamException('Invalid local file.');
      }
      return _config!.localFile.length().then((size) {
        return checkStorageCapacityImpl(size);
      });
    }
    return checkStorageCapacityImpl(size);
  }

  Future<bool> checkStorageCapacityImpl(int size) {
    return protocol.getRemainStorageCapacity().then((remainSize) {
      bool enough = isStorageCapacityEnough(remainSize, size);
      if (enough) return true;
      // TODO: 清除临时文件后再试
//            return clearCacheFiles().then((cleared) {
//                if (cleared) return protocol.getRemainStorageCapacity().then((remainSize)=>isStorageCapacityEnough(remainSize, size));
//                return false;
//            });
      return false;
    }).onError((Exception error, stackTrace) {
      throw error;
    });
  }

  bool isStorageCapacityEnough(int remain, int target) {
    return remain - target >= 10 * 1024;
  }

  int fileOperationTargetDevice() {
    return DeviceUtils.getHmicDevice(protocol.productProperty.model);
  }

  Future<bool> clearCacheFiles();

  Future renameFileIfNeed(int id, StreamController<UploadEvent> streamController);

  void cancel() {
    canceled = true;
    if (_fileManager != null) _fileManager!.interrupt();
  }

  void destroy() {
    try {
      streamController.close();
    } catch (ignore) {}
    try {
      if (_fileManager != null) {
        _fileManager!.interrupt();
        _fileManager!.destroy();
      }
    } catch (ignore) {}
  }

  MediaUploadConfig? get getConfig => _config;

  IFileManager? get fileManager => _fileManager;
}

class UploadEvent {
  static const typeCanceled = 0;
  static const typeCheckStorageCapacity = 1;
  static const typeFileOpened = 2;
  static const typeFileTransferring = 3;
  static const typeFileChecked = 4;
  static const typeFileClosed = 5;
  static const typeFinished = 6;

  int type = -1;

  UploadEvent(this.type);

  UploadEvent.byCanceled() : this(typeCanceled);

  UploadEvent.byCheckStorageCapacity() : this(typeCheckStorageCapacity);

  UploadEvent.byFileOpened() : this(typeFileOpened);

  UploadEvent.byFileChecked() : this(typeFileChecked);

  UploadEvent.byFileClosed() : this(typeFileClosed);

  UploadEvent.byFinished() : this(typeFinished);

  @override
  String toString() {
    return 'UploadEvent{type: $type}';
  }
}

class FileTransferringEvent extends UploadEvent {
  final FileProgressEvent _fileProgressEvent;

  FileTransferringEvent(this._fileProgressEvent) : super(UploadEvent.typeFileTransferring);

  /// 当前进度
  int progress() {
    return _fileProgressEvent.progress;
  }

  /// 总大小
  int total() {
    return _fileProgressEvent.total;
  }

  /// 平均速度
  int aveSpeed() {
    return _fileProgressEvent.aveSpeed;
  }

  /// 实时速度
  int rtSpeed() {
    return _fileProgressEvent.rtSpeed;
  }

  /// 已用时间
  int costSeconds() {
    return _fileProgressEvent.costSeconds;
  }

  /// 预计剩余时间
  int remainSeconds() {
    return _fileProgressEvent.remainSeconds;
  }

  @override
  String toString() {
    return 'FileTransferringEvent{_fileProgressEvent: $_fileProgressEvent}';
  }
}

class MediaFileUploadError extends InMotionProtocolException {
  static const stepModeCheck = 1;
  static const stepCheckStorageCapacity = 2;
  static const stepClearCache = 3;
  static const stepFileOperation = 4;
  static const stepRenameFile = 5;

  static const errorStorageCapacityNotEnough = 0x80;

  static const errorNotNow = 3;
  static const errorTimeout = 0xFF;

  int step;
  int channel;
  int property;
  int error;
  int? extra;

  MediaFileUploadError(this.step, this.channel, this.property, this.error, [this.extra]);

  static MediaFileUploadError byModeCheck(int error) {
    return MediaFileUploadError(stepModeCheck, 0, 0, error);
  }

  static MediaFileUploadError byStorageCapacityNotEnough() {
    return MediaFileUploadError(stepCheckStorageCapacity, Constant.channelStorage, 1, errorStorageCapacityNotEnough);
  }

  static MediaFileUploadError byClearCache(int error) {
    return MediaFileUploadError(stepClearCache, Constant.channelFile, Constant.propertyDeleteFile, error);
  }

  static MediaFileUploadError byRenameFile(int error) {
    return MediaFileUploadError(stepRenameFile, Constant.channelFile, Constant.propertyRenameFile, error);
  }

  static MediaFileUploadError byFileOperationStepError(FileOperationStepError error) {
    return MediaFileUploadError(stepFileOperation, Constant.channelFile, error.step, error.error, error.extra == FileOperationStepError.noExtra ? null : error.extra);
  }

  bool isStorageCapacityNotEnough() {
    return step == stepCheckStorageCapacity && channel == Constant.channelStorage && property == 1 && error == errorStorageCapacityNotEnough;
  }

  bool isNotNowError() {
    return step == stepModeCheck && channel == 0 && property == 0 && error == errorNotNow;
  }

  String genDisplayErrorCode() {
    return "0x${ByteUtil.byte2Hex(step)}${ByteUtil.byte2Hex(channel)}${ByteUtil.byte2Hex(property)}${ByteUtil.byte2Hex(error)}${extra == null ? '' : ByteUtil.byte2Hex(extra!)}";
  }

  /// 使用分隔符[split]格式化错误码
  /// 一般用于显示
  String formatErrorCode([String split = ', ']) {
    StringBuffer stringBuffer = StringBuffer(ByteUtil.byte2Hex(step));
    stringBuffer.write(split);
    stringBuffer.write(ByteUtil.byte2Hex(channel));
    stringBuffer.write(split);
    stringBuffer.write(ByteUtil.byte2Hex(property));
    stringBuffer.write(split);
    stringBuffer.write(ByteUtil.byte2Hex(error));
    if (extra != null && extra != 0xFFFFFFFF) {
      stringBuffer.write(split);
      stringBuffer.write(ByteUtil.numberLe2HexString(extra!));
    }
    return stringBuffer.toString().toUpperCase();
  }
}

class StorageCapacityNotEnoughException extends InMotionProtocolException {}

class FileRenameFailedException extends InMotionProtocolException {}

abstract class MediaUploadConfig implements FileConfig {
  int id = 0;
}

abstract class SoundFileUploadManager extends MediaPkgUploadManager {
  SoundFileUploadManager(Protocol<IMessage> protocol) : super(protocol);

  MediaUploadConfig createSoundPackConfig(File localFile, int pkgId);

  MediaUploadConfig createSoundItemConfig(File localFile, int itemIndex);
}

abstract class LightEffectPkgFileUploadManager extends MediaPkgUploadManager {
  LightEffectPkgFileUploadManager(Protocol<IMessage> protocol) : super(protocol);

  MediaUploadConfig createConfig(File localFile, int pkgId);
}
